#define __NBTTAG__

#include "NBTTagList.h"
#include "NBTTypeException.h"

#include <iostream>
#include <string>
#include <vector>
#include <iterator>
#include <sstream>
#include <queue>

#ifndef byte
typedef unsigned char byte;
#endif

#ifndef __STRING__
#include "..\..\..\Paril\Utils\String.h"
#endif

class NBTTag
{
	/// <summary>
	/// Tag string values as usually represented in text.
	/// </summary>
	static const char *GetTagString(const int &index)
	{
		static const char *TagStrings[] =
		{
			"TAG_End",
			"TAG_Byte",
			"TAG_Short",
			"TAG_Int",
			"TAG_Long",
			"TAG_Float",
			"TAG_Double",
			"TAG_Byte_Array",
			"TAG_String",
			"TAG_List",
			"TAG_Compound",
			"TAG_Int_Array"
		}; 

		return TagStrings[index];
	}

public:
	/// <summary>
	/// The type that this tag represents.
	/// </summary>
	NBTType Type;

	/// <summary>
	/// The name, if any, of this tag.
	/// </summary>
	std::string Name;

	/// <summary>
	/// The raw object that this tag has, if any.
	/// </summary>
	void *RawValue;

	/// <summary>
	/// The parent tag of this tag.
	/// </summary>
	NBTTag *Parent;

	NBTTag() :
		Type(TAG_End),
		Name(),
		RawValue(nullptr),
		Parent(nullptr)
	{
	}

	NBTTag(void *value) :
		Type(TAG_End),
		Name(),
		Parent(nullptr),
		RawValue(value)
	{
	};

	~NBTTag()
	{
		if (RawValue != nullptr)
			delete RawValue;
	};

	/// <summary>
	/// Retrieve a textual representation of the tag.
	/// </summary>
	/// <returns>A textual representation of this tag.</returns>
	std::string ToString() const
	{
		std::stringstream str;

		if (!Name.empty())
			str << Name << " [" << GetTagString(Type) << "]";
		else
			str << GetTagString(Type);

		return str.str();
	}

	/// <summary>
	/// Convert and return the value of the raw tag.
	/// </summary>
	/// <typeparam name="T">The type of tag value to return</typeparam>
	/// <returns>The tag value, casted as the type you need</returns>
	template<typename T>
	T &GetValue() const
	{
		return *static_cast<T*>(RawValue);
	}

	/// <summary>
	/// Get this tag's string value.
	/// </summary>
	/// <returns>The tag's string value</returns>
	std::string &GetString() const { return GetValue<std::string>(); }

	/// <summary>
	/// Get this tag's byte value.
	/// </summary>
	/// <returns>The tag's byte value</returns>
	byte &GetByte() const { return GetValue<byte>(); }
		
	/// <summary>
	/// Get this tag's short value
	/// </summary>
	/// <returns>The tag's short value</returns>
	short &GetInt16() const { return GetValue<short>(); }

	/// <summary>
	/// Get this tag's int value
	/// </summary>
	/// <returns>The tag's int value</returns>
	int &GetInt32() const { return GetValue<int>(); }

	/// <summary>
	/// Get this tag's long value
	/// </summary>
	/// <returns>The tag's long value</returns>
	long long &GetInt64() const { return GetValue<long long>(); }

	/// <summary>
	/// Get this tag's float value
	/// </summary>
	/// <returns>The tag's float value</returns>
	float &GetSingle() const { return GetValue<float>(); }

	/// <summary>
	/// Get this tag's double value
	/// </summary>
	/// <returns>The tag's double value</returns>
	double &GetDouble() const { return GetValue<double>(); }

	/// <summary>
	/// Get this tag's list of child tags (compound tag)
	/// </summary>
	/// <returns>The list of children tags</returns>
	std::vector<NBTTag*> &GetCompound() const { return GetValue<std::vector<NBTTag*>>(); }

	/// <summary>
	/// Get this tag's array of integers
	/// </summary>
	/// <returns>The list of integers</returns>
	std::vector<int> &GetIntArray() const { return GetValue<std::vector<int>>(); }

	/// <summary>
	/// Get this tag's array of bytes
	/// </summary>
	/// <returns>The list of bytes</returns>
	std::vector<byte> &GetByteArray() const { return GetValue<std::vector<byte>>(); }
		
	/// <summary>
	/// Get this tag's generic list
	/// </summary>
	/// <returns>The tag's generic list</returns>
	NBTTagList &GetList() const { return GetValue<NBTTagList>(); }

	template<typename T, typename C>
	static T &popAndReturn(std::queue<T, C> &queue)
	{
		auto &front = queue.front();
		queue.pop();
		return front;
	}

	/// <summary>
	/// Find a child tag that matches this name. This method supports
	/// chained nodes, separated by commas.
	/// </summary>
	/// <example>
	/// Find a single child node: Find("Root")
	/// Find a chained sub-node: Find("Root.Inventory");
	/// </example>
	/// <param name="name">The name or name chain to find.</param>
	/// <returns>The tag it found, else null.</returns>
	const NBTTag *Find(const std::string &name) const
	{
		if (name.find_first_of('.') != std::string::npos)
		{
			std::queue<std::string> nodes(String::Split<std::deque<std::string>>(name, ".", false));
			
			const NBTTag *node;
			for (node = this; nodes.size() != 0 && node != nullptr; node = node->Find(popAndReturn(nodes))) ;

			return node;
		}
		else
		{
			auto compound = GetCompound();

			for (auto it = compound.begin(); it != compound.end(); ++it)
			{
				auto &c = (*it);

				if (c->Name == name)
					return c;
			}
		}

		return nullptr;
	}
};